#include "GLES2HardwareIndexBuffer.h"
#include "GLES2HardwareBufferManager.h"
#include "GLES2RenderSystem.h"
#include "Root.h"

namespace PVM
{
	GLES2HardwareIndexBuffer::GLES2HardwareIndexBuffer(HardwareBufferManagerBase* mgr,
													   IndexType idxType,
													   size_t numIndexes,
													   HardwareBuffer::Usage usage,
													   bool useShadowBuffer)
	: HardwareIndexBuffer(mgr, idxType, numIndexes, usage, false, useShadowBuffer)
	{
		if (idxType == HardwareIndexBuffer::IT_32BIT)
		{
			PVM_EXCEPT("32 bit hardware buffers not allowed in OpenGL ES.",
					   "GLES2HardwareIndexBuffer");
		}
		
		if (!useShadowBuffer)
		{
			PVM_EXCEPT("Only support with shadowBuffer",
					   "GLES2HardwareIndexBuffer");
		}
		
		glGenBuffers(1, &mBufferId);
		if (!mBufferId)
		{
			PVM_EXCEPT("Cannot create GL ES index buffer",
					   "GLES2HardwareIndexBuffer::GLES2HardwareIndexBuffer");
		}
		
		dynamic_cast<GLES2RenderSystem*>(Root::getSingleton().getRenderSystem())->_bindGLBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferId);
		
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, NULL,
					 GLESHardwareBufferManager::getGLUsage(usage));
	}
	
	GLES2HardwareIndexBuffer::~GLES2HardwareIndexBuffer()
	{
		glDeleteBuffers(1, &mBufferId);
	}
	
	void GLES2HardwareIndexBuffer::unlockImpl(void)
	{
		if (mLockedToScratch)
		{
			if (mScratchUploadOnUnlock)
			{
				writeData(mScratchOffset, mScratchSize, mScratchPtr,
						  mScratchOffset == 0 && mScratchSize == getSizeInBytes());
			}
			
			static_cast<GLES2HardwareBufferManager*>(
				HardwareBufferManager::getSingletonPtr())->deallocateScratch(mScratchPtr);
			mLockedToScratch = false;
		}
		else 
		{
			dynamic_cast<GLES2RenderSystem*>(Root::getSingleton().getRenderSystem())->_bindGLBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferId);
			
			if (!glUnmapBufferOES(GL_ELEMENT_ARRAY_BUFFER))
			{
				PVM_EXCEPT("Buffer data corrupted, please reload",
						   "GLES2HardwareIndexBuffer::unlock");
			}
		}
		m_IsLocked = false;
	}
	
	void* GLES2HardwareIndexBuffer::lockImpl(size_t offset,
											size_t length,
											 LockOptions options)
	{
		if (mIsLocked)
		{
			PVM_EXCEPT("Invalid attempt to lock an index buffer that has already been locked",
					   "GLES2HardwareIndexBuffer::lock");
		}
		
		void* retPtr = 0;
		GLenum access = 0;
		GLES2HardwareBufferManager* glBufManager = static_cast<GLES2HardwareBufferManager*>(HardwareBufferManager::getSingletonPtr());
		
		if (length < glBufManager->getGLMapBufferThreshold())
		{
			retPtr = glBufManager->allocateScratch((uint32)length);
			if (retPtr)
			{
				mLockedToScratch = true;
				mScratchOffset = offset;
				mScratchSize = length;
				mScratchPtr = retPtr;
				mScratchUploadOnUnlock = (options != HBL_READ_ONLY);
			
			
				if (options != HBL_DISCARD)
				{
					readData(offset, length, retPtr);
				}
			}
		}
		else
		{
			PVM_EXCEPT("Invalid Buffer lockSize",
					   "GLES2HardwareIndexBuffer::lock");
		}

		if (!retPtr)
		{
			dynamic_cast<GLES2RenderSystem*>(Root::getSingleton().getRenderSystem())->_bindGLBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferId);
			if (options == HBL_DISCARD)
			{
				glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, NULL,
							 GLES2HardwareBufferManager::getGLUsage(mUsage));
			}
			if (mUsage & HBU_WRITE_ONLY)
				access = GL_WRITE_ONLY_OES;
			
			void* pBuffer = glMapBufferOES(GL_ELEMENT_ARRAY_BUFFER, access);
			
			if (pBuffer == 0)
			{
				PVM_EXCEPT("Index Buffer: Out of memory",
						   "GLES2HardwareIndexBuffer::lock");
			}
			
			retPtr = static_cast<void*>(
				static_cast<unsigned char*>(pBuffer) + offset);
			
			mLockedToScratch = false;
		}
		
		mIsLocked = true;
		return retPtr;
	}
	
	void GLES2HardwareIndexBuffer::readData(size_t offset,
											size_t length,
											void* pDest)
	{
		if (mUseShadowBuffer)
		{
			void* srcData = mpShadowBuffer->lock(offset, length, HBL_READ_ONLY);
			memcpy(pDest, srcData, length);
			mpShadowBuffer->unlock();
		}
		else 
		{
			PVM_EXCEPT("Reading hardware buffer is not supported",
					   "GLES2HardwareIndexBuffer::readData");
		}
	}
	
	void GLES2HardwareIndexBuffer::writeData(size_t offset, size_t length,
											 const void* pSource,
											 bool discardWholeBuffer)
	{
		dynamic_cast<GLES2RenderSystem*>(Root::getSingleton().getRenderSystem())->_bindGLBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferId);
		
		if (mUseShadowBuffer)
		{
			void* destData = mpShadowBuffer->lock(offset, length,
												  discardWholeBuffer ? HBL_DISCARD : HBL_NORMAL);
			memcpy(destData, pSource, length);
			mpShadowBuffer->unlock();
		}
		
		if (offset == 0 && length == mSizeInBytes)
		{
			glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, pSource,
						 GLES2HardwareBufferManager::getGLUsage(mUsage));
		}
		else
		{
			if (discardWholeBuffer)
			{
				glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, NULL,
							 GLES2HardwareBufferManager::getGLUsage(mUsage));
			}
			
			glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, length, pSource);
		}
	}
	
	void GLES2HardwareIndexBuffer::_updateFromShadow(void)
	{
		if (mUseShadowBuffer && mShadowUpdated && !mSuppressHardwareUpdate)
		{
			const void* srcData = mpShadowBuffer->lock(mLockStart, mLockSize,
													   HBL_READ_ONLY);
			
			dynamic_cast<GLES2RenderSystem*>(Root::getSingleton().getRenderSystem())->_bindGLBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferId);
			
			if (mLockStart == 0 && mLockSize == mSizeInBytes)
			{
				glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, srcData,
							 GLES2HardwareBufferManager::getGLUsage(mUsage));
			}
			else
			{
				glBufferSubData(GL_ELEMENT_ARRAY_BUFFER,
							 mLockStart, mLockSize, srcData);
			}
			
			mpShadowBuffer->unlock();
			mShadowUpdated = false;
		}
	}
}

